package com.qiaoba.module.job.service.impl;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qiaoba.api.job.constant.JobConstant;
import com.qiaoba.api.job.entity.dto.SysJobDTO;
import com.qiaoba.api.job.entity.vo.SysJobVO;
import com.qiaoba.common.base.constant.BaseConstant;
import com.qiaoba.common.base.exception.ServiceException;
import com.qiaoba.module.job.converter.SysJobConverter;
import com.qiaoba.module.job.entity.SysJob;
import com.qiaoba.module.job.entity.param.SysJobParam;
import com.qiaoba.module.job.mapper.SysJobMapper;
import com.qiaoba.module.job.service.SysJobService;
import com.qiaoba.module.job.util.CronUtil;
import com.qiaoba.module.job.util.ScheduleUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * 定时任务 服务层实现
 *
 * @author ailanyin
 * @date 2023-09-03 21:51:14
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SysJobServiceImpl implements SysJobService {

    private final SysJobMapper sysJobMapper;
    private final Scheduler scheduler;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insert(SysJobDTO dto) throws Exception {
        check(dto);
        SysJob sysJob = SysJobConverter.dto2Entity(dto);
        // 暂停任务
        sysJob.setStatus(JobConstant.PAUSE);
        int result = sysJobMapper.insert(sysJob);
        if (result > 0) {
            // 新建quartz任务
            ScheduleUtil.createScheduleJob(scheduler, sysJob);
        }

        return result;
    }

    @Override
    public int updateStatus(String jobId, String status) {
        int rows = 0;
        SysJob sysJob = selectById(jobId, false);
        if (JobConstant.NORMAL.equals(status)) {
            rows = resumeJob(sysJob);
        } else if (JobConstant.PAUSE.equals(status)) {
            rows = pauseJob(sysJob);
        }
        return rows;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteById(String jobId) throws Exception {
        SysJob sysJob = selectById(jobId, false);
        int result = sysJobMapper.deleteById(jobId);
        if (result > 0) {
            // 删除quartz任务
            scheduler.deleteJob(ScheduleUtil.getJobKey(jobId, sysJob.getJobGroup()));
        }
        return result;
    }

    @Override
    public SysJobVO selectVoById(String jobId, boolean allowNull) {
        SysJob sysJob = selectById(jobId, allowNull);
        return Objects.isNull(sysJob) ? null : SysJobConverter.domain2VO(sysJob);
    }

    @Override
    public SysJob selectById(String jobId) {
        return selectById(jobId, true);
    }

    @Override
    public SysJob selectById(String jobId, boolean allowNull) {
        SysJob sysJob = sysJobMapper.selectById(jobId);
        if (!allowNull && Objects.isNull(sysJob)) {
            throw new ServiceException(StrUtil.format("找不到ID为{}的定时任务", jobId));
        }
        return sysJob;
    }

    @Override
    public int run(String jobId) {
        log.info("立即运行定时任务, 任务ID:{}", jobId);
        SysJob sysJob = selectById(jobId, false);
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(JobConstant.TASK_PROPERTIES, sysJob);
        JobKey jobKey = ScheduleUtil.getJobKey(jobId, sysJob.getJobGroup());
        try {
            if (scheduler.checkExists(jobKey)) {
                scheduler.triggerJob(jobKey, dataMap);
            }
            return 1;
        } catch (SchedulerException e) {
            log.error("立即运行定时任务发生异常: " + ExceptionUtil.getMessage(e));
        }
        return 0;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateById(SysJobDTO dto) throws Exception {
        check(dto);
        SysJob newJob = SysJobConverter.dto2Entity(dto);
        SysJob olgJob = selectById(newJob.getJobId(), false);
        int result = sysJobMapper.updateById(newJob);
        if (result > 0) {
            // 处理quartz任务状态
            updateSchedulerJob(newJob, olgJob.getJobGroup());
        }

        return result;
    }

    @Override
    public int pauseJob(SysJob sysJob) {
        String jobId = sysJob.getJobId();
        String jobGroup = sysJob.getJobGroup();
        sysJob.setStatus(JobConstant.PAUSE);
        int result = sysJobMapper.updateById(sysJob);
        if (result > 0) {
            try {
                scheduler.pauseJob(ScheduleUtil.getJobKey(jobId, jobGroup));
            } catch (SchedulerException e) {
                log.error("暂停定时任务发生异常: " + ExceptionUtil.getMessage(e));
            }
        }
        return result;
    }

    @Override
    public int resumeJob(SysJob sysJob) {
        String jobId = sysJob.getJobId();
        String jobGroup = sysJob.getJobGroup();
        sysJob.setStatus(JobConstant.NORMAL);
        int result = sysJobMapper.updateById(sysJob);
        if (result > 0) {
            try {
                scheduler.resumeJob(ScheduleUtil.getJobKey(jobId, jobGroup));
            } catch (SchedulerException e) {
                log.error("恢复定时任务发生异常: " + ExceptionUtil.getMessage(e));
            }
        }
        return result;
    }

    @Override
    public List<SysJob> selectList(SysJobParam param) {
        return sysJobMapper.selectList(param2Wrapper(param));
    }

    private Wrapper<SysJob> param2Wrapper(SysJobParam param) {
        LambdaQueryWrapper<SysJob> wrapper = new LambdaQueryWrapper<>();
        wrapper
                .like(StrUtil.isNotBlank(param.getJobName()), SysJob::getJobName, param.getJobName())
                .eq(StrUtil.isNotBlank(param.getJobGroup()), SysJob::getJobGroup, param.getJobGroup())
                .eq(StrUtil.isNotBlank(param.getStatus()), SysJob::getStatus, param.getStatus());
        return wrapper;
    }

    /**
     * 更新任务
     *
     * @param job      任务对象
     * @param jobGroup 任务组名
     */
    private void updateSchedulerJob(SysJob job, String jobGroup) throws Exception {
        String jobId = job.getJobId();
        // 判断是否存在
        JobKey jobKey = ScheduleUtil.getJobKey(jobId, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            // 防止创建时存在数据问题 先移除，然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtil.createScheduleJob(scheduler, job);
    }

    private void check(SysJobDTO job) {
        if (!CronUtil.isValid(job.getCronExpression())) {
            throw new ServiceException("保存任务失败，Cron表达式不正确");
        } else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), BaseConstant.LOOKUP_RMI)) {
            throw new ServiceException("保存任务失败，目标字符串不允许'rmi'调用");
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{BaseConstant.LOOKUP_LDAP, BaseConstant.LOOKUP_LDAPS})) {
            throw new ServiceException("保存任务失败，目标字符串不允许'ldap(s)'调用");
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{BaseConstant.HTTP, BaseConstant.HTTPS})) {
            throw new ServiceException("保存任务失败，目标字符串不允许'http(s)'调用");
        } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), JobConstant.JOB_ERROR_STR)) {
            throw new ServiceException("保存任务失败，目标字符串存在违规");
        } else if (!ScheduleUtil.isWhiteList(job.getInvokeTarget())) {
            throw new ServiceException("保存任务失败，目标字符串不在白名单内");
        }
    }
}
